#include <defs.h>
#include <riscv.h>
#include <stdio.h>
#include <string.h>
#include <swap.h>
#include <swap_LRU.h>
#include <list.h>

extern list_entry_t pra_list_head;
static int _lru_check(struct mm_struct *mm);

//pra_list 是 Page Replacement Algorithm list 的缩写，
//通常用于在实现页面置换算法（Page Replacement Algorithm, PRA）时，维护页面的一个链表结构。
static int _lru_init_mm(struct mm_struct *mm)//初始化LRU页面替换算法
{
    list_init(&pra_list_head);  //调用 list_init 初始化 pra_list_head 链表头。  
    mm->sm_priv = &pra_list_head; //设置 mm->sm_priv 为 pra_list_head 的地址，用于保存页面链表的头指针。
    cprintf(" mm->sm_priv %x in fifo_init_mm\n", mm->sm_priv);//打印 mm->sm_priv 的值。
    return 0;
}

//用于将页面标记为可交换并将其添加到页面链表中。这个函数是基于 LRU（Least Recently Used，最近最少使用） 页面置换算法设计的
//主要作用是将新加载到内存中的页面添加到 LRU 链表的末尾，并初始化其访问状态。
static int _lru_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in)
{
    _lru_check(mm);//调用 _lru_check 检查页面访问情况。
    list_entry_t *entry = &(page->pra_page_link);//将页面的链表节点 entry 添加到 pra_list_head 链表中（表示该页面现在处于内存中）。
    assert(entry != NULL);
    list_entry_t *head = (list_entry_t *)mm->sm_priv;
    list_add(head, entry); // 将页面page插入到页面链表pra_list_head的末尾
    page->visited = 0;    //将页面的 visited 属性设置为 0，表示页面当前未被访问。
    return 0;
}
//用于在内存中选择一个页面进行置换（即将其换出到磁盘或交换空间），并且基于 LRU（Least Recently Used） 页面置换策略。
//LRU 策略会优先换出最久未被访问的页面，这里通过页面的 visited 值来判断页面的访问情况。
//struct mm_struct *mm：指向进程的内存管理结构体 mm_struct，包含该进程的内存布局和管理信息。
//struct Page **ptr_page：指向页面指针的指针，用于返回选中的页面（要换出的页面）。
//int in_tick：表示是否在时钟中断（tick interrupt）中调用，这里要求 in_tick 为 0（即不在时钟中断中调用）。
static int _lru_swap_out_victim(struct mm_struct *mm, struct Page **ptr_page, int in_tick)
{
    _lru_check(mm);//调用 _lru_check 函数检查页面访问情况，更新页面链表中每个页面的 visited 值。
    list_entry_t *head = (list_entry_t *)mm->sm_priv;//将 mm->sm_priv 转换为链表头指针 head。
    assert(head != NULL);//确保 head 不为空。
    assert(in_tick == 0);//确保函数不是在时钟中断中调用的。
    
    list_entry_t *entry = list_prev(head);//将 entry 设置为链表中最后一个页面（即 head 的前一个节点）。
    list_entry_t *pTobeDel = entry;//用于存储要换出的页面节点指针，初始值设为 entry。
    //存储页面 visited 值的最大值（即最长时间未被访问的页面的 visited 值），初始值设为当前 entry 的 visited 值。
    uint_t largest_visted = le2page(entry, pra_page_link)->visited;     
    while (1)
    {
        //无限循环遍历链表。当 entry 回到链表头时，表示遍历完成，跳出循环。
        //如果当前节点 entry 的 visited 值大于 largest_visted，则更新 largest_visted 和 pTobeDel。
        //pTobeDel 始终指向 visited 值最大的页面节点，即最久未被访问的页面。
        //entry = list_prev(entry)：将 entry 移动到前一个节点。
        if (entry == head)
        {
            break;
        }
        if (le2page(entry, pra_page_link)->visited > largest_visted)
        {
            largest_visted = le2page(entry, pra_page_link)->visited;
            // le2page(entry, pra_page_link)->visited = 0;
            pTobeDel = entry;
            // curr_ptr = entry;
        }
        entry = list_prev(entry);
    }
    list_del(pTobeDel);//list_del(pTobeDel) 从链表中删除 pTobeDel 指向的节点，即选择的最久未被访问的页面。
    *ptr_page = le2page(pTobeDel, pra_page_link);//将 pTobeDel 转换为页面指针并赋值给 *ptr_page，将被选中换出的页面返回给调用者。
    cprintf("curr_ptr %p\n", pTobeDel);//打印当前指针的值
    return 0;
}

static int _lru_check(struct mm_struct *mm)
{
    cprintf("\nbegin check----------------------------------\n");//打印开始检查的信息
    list_entry_t *head = (list_entry_t *)mm->sm_priv;  //head指向页面链表的头节点，mm->sm_priv 是 mm_struct 结构中的私有指针，用来指向页面链表的头节点 pra_list_head。
    assert(head != NULL);
    list_entry_t *entry = head;
    while ((entry = list_prev(entry)) != head)//从链表的末尾开始向前遍历，每次循环将 entry 移动到前一个节点。
    {
        struct Page *entry_page = le2page(entry, pra_page_link);//le2page(entry, pra_page_link) 将链表节点 entry 转换为对应的 Page 结构体指针 entry_page，表示当前页面。
        pte_t *tmp_pte = get_pte(mm->pgdir, entry_page->pra_vaddr, 0);
        //get_pte(mm->pgdir, entry_page->pra_vaddr, 0)：获取当前页面的页表项 tmp_pte，这是该页面的虚拟地址 entry_page->pra_vaddr 在页表中的条目。
        //通过 tmp_pte 可以访问该页面的访问标志位。
        //(*tmp_pte) >> 10 右移 10 位，打印页表项中页面物理页号（PPN）的值。PPN 是页表项的主要内容之一，表示物理地址中的页框编号。
        cprintf("the ppn value of the pte of the vaddress is: 0x%x  \n", (*tmp_pte) >> 10);
        //*tmp_pte & PTE_A 检查页表项中的访问标志位 PTE_A。
        //如果该标志位为 1，表示该页面在近期被访问过。
        //将 entry_page->visited 设置为 0，表示该页面刚刚被访问。
        //使用 *tmp_pte = *tmp_pte ^ PTE_A 将访问位清零，以便下次访问时可以重新检测。
        if (*tmp_pte & PTE_A)  //如果近期被访问过，visited清零(visited越大表示越长时间没被访问)
        {
            entry_page->visited = 0;
            *tmp_pte = *tmp_pte ^ PTE_A;//清除访问位
        }
        //未被访问就加1
        else
        {
            entry_page->visited++;
        }

        cprintf("the visited goes to %d\n", entry_page->visited);
    }
    cprintf("end check------------------------------------\n\n");
}

static int _lru_check_swap(void)
{
#ifdef ucore_test
    int score = 0, totalscore = 5;
    cprintf("%d\n", &score);
    ++score;
    cprintf("grading %d/%d points", score, totalscore);
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num == 4);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num == 4);
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num == 4);
    *(unsigned char *)0x2000 = 0x0b;
    ++score;
    cprintf("grading %d/%d points", score, totalscore);
    assert(pgfault_num == 4);
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num == 5);
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num == 5);
    ++score;
    cprintf("grading %d/%d points", score, totalscore);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num == 5);
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num == 5);
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num == 5);
    ++score;
    cprintf("grading %d/%d points", score, totalscore);
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num == 5);
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num == 5);
    assert(*(unsigned char *)0x1000 == 0x0a);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num == 6);
    ++score;
    cprintf("grading %d/%d points", score, totalscore);
#else
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num == 4);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num == 4);
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num == 4);
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num == 4);
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num == 5);
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num == 5);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num == 5);
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num == 5);
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num == 5);
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num == 5);
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num == 5);

    assert(*(unsigned char *)0x1000 == 0x0a);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num == 6);
#endif
    return 0;
}

static int _lru_init(void)
{
    return 0;
}

static int _lru_set_unswappable(struct mm_struct *mm, uintptr_t addr)
{
    return 0;
}

static int _lru_tick_event(struct mm_struct *mm)
{
    return 0;
}

struct swap_manager swap_manager_lru =
    {
        .name = "lru swap manager",
        .init = &_lru_init,
        .init_mm = &_lru_init_mm,
        .tick_event = &_lru_tick_event,
        .map_swappable = &_lru_map_swappable,
        .set_unswappable = &_lru_set_unswappable,
        .swap_out_victim = &_lru_swap_out_victim,
        .check_swap = &_lru_check_swap,
};
